/*   Copyright © 2009 Mark Gil Torres
 *
 *   This file is part of mtoolkit.
 *
 *   mtoolkit is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   any later version.
 *
 *   mtoolkit is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with mtoolkit.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef INTEGER_H
#define INTEGER_H

#include <gmpxx.h>
#include <iostream>
#include <string>

//! namespace mt
namespace mt {

 /*! Integer Class
  *
  * An unbounded integer datatype which uses the
  * GNU Multiple Precision Arithmetic library "mpz_class".
  *
  */
  typedef unsigned long int uli_t;
  class Integer {
    public:
      Integer(void); //! default constructor which sets the default value to 0.
      Integer(Integer const& inValue); //! copy constructor

      Integer(int const& inValue); //! alternative constructor using C++ primitive integers which sets the default value to an integer between [-2147483648,2147483647].
      Integer(std::string const& inValue); //! alternative constructor using C++ string which sets the default value to an integer between (-inf,+inf).

      Integer& operator=(int const& inValue);
      Integer& operator=(std::string const& inValue);
      Integer& operator=(Integer const& inValue);

      mpz_class getValue(void);
      void setValue(mpz_class const& inValue);

      friend Integer const operator+(Integer const& inValue1, int const& inValue2);
      friend Integer const operator-(Integer const& inValue1, int const& inValue2);
      friend Integer const operator*(Integer const& inValue1, int const& inValue2);
      friend Integer const operator/(Integer const& inValue1, int const& inValue2);
      friend Integer const operator%(Integer const& inValue1, int const& inValue2);
      friend Integer const operator+(int const& inValue1, Integer const& inValue2);
      friend Integer const operator-(int const& inValue1, Integer const& inValue2);
      friend Integer const operator*(int const& inValue1, Integer const& inValue2);
      friend Integer const operator/(int const& inValue1, Integer const& inValue2);
      friend Integer const operator%(int const& inValue1, Integer const& inValue2);
      friend Integer const operator+(Integer const& inValue1, Integer const& inValue2);
      friend Integer const operator-(Integer const& inValue1, Integer const& inValue2);
      friend Integer const operator*(Integer const& inValue1, Integer const& inValue2);
      friend Integer const operator/(Integer const& inValue1, Integer const& inValue2);
      friend Integer const operator%(Integer const& inValue1, Integer const& inValue2);

      friend std::ostream& operator<<(std::ostream& ostr, Integer const& inValue);
      friend std::istream& operator>>(std::istream& istr, Integer& inValue);

      friend bool operator<(Integer const& inValue1, int const& inValue2);
      friend bool operator<=(Integer const& inValue1, int const& inValue2);
      friend bool operator>(Integer const& inValue1, int const& inValue2);
      friend bool operator>=(Integer const& inValue1, int const& inValue2);
      friend bool operator==(Integer const& inValue1, int const& inValue2);
      friend bool operator<(int const& inValue1, Integer const& inValue2);
      friend bool operator<=(int const& inValue1, Integer const& inValue2);
      friend bool operator>(int const& inValue1, Integer const& inValue2);
      friend bool operator>=(int const& inValue1, Integer const& inValue2);
      friend bool operator==(int const& inValue1, Integer const& inValue2);
      friend bool operator<(Integer const& inValue1, Integer const& inValue2);
      friend bool operator<=(Integer const& inValue1, Integer const& inValue2);
      friend bool operator>(Integer const& inValue1, Integer const& inValue2);
      friend bool operator>=(Integer const& inValue1, Integer const& inValue2);
      friend bool operator==(Integer const& inValue1, Integer const& inValue2);

      friend Integer const abs(Integer const& inValue);
      friend Integer const gcd(Integer const& inValue1, Integer const& inValue2);
      friend Integer const lcm(Integer const& inValue1, Integer const& inValue2);
      friend Integer const pow(Integer& inValue1, uli_t const& inValue2);

    private:
      mpz_class mValue;
  };

  Integer::Integer(void) {
    mValue = 0;
  }

  Integer::Integer(Integer const& inValue) : mValue(inValue.mValue) {}

  Integer::Integer(int const& inValue) : mValue(inValue) {}

  Integer::Integer(std::string const& inValue) : mValue(inValue) {}

  Integer& Integer::operator=(int const& inValue) {
    mValue = inValue;
    return *this;
  }

  Integer& Integer::operator=(std::string const& inValue) {
    mValue = inValue;
    return *this;
  }

  Integer& Integer::operator=(Integer const& inValue) {
    mValue = inValue.mValue;
    return *this;
  }

  mpz_class Integer::getValue(void) {
    return mValue;
  }

  void Integer::setValue(mpz_class const& inValue) {
    mValue = inValue;
  }

  Integer const operator+(Integer const& inValue1, int const& inValue2) {
    Integer temp;
    mpz_class inValue2Temp;
    inValue2Temp = inValue2;
    temp.mValue = inValue1.mValue + inValue2Temp;
    return temp;
  }

  Integer const operator-(Integer const& inValue1, int const& inValue2) {
    Integer temp;
    mpz_class inValue2Temp;
    inValue2Temp = inValue2;
    temp.mValue = inValue1.mValue - inValue2Temp;
    return temp;
  }

  Integer const operator*(Integer const& inValue1, int const& inValue2) {
    Integer temp;
    mpz_class inValue2Temp;
    inValue2Temp = inValue2;
    temp.mValue = inValue1.mValue * inValue2Temp;
    return temp;
  }

  Integer const operator/(Integer const& inValue1, int const& inValue2) {
    Integer temp;
    mpz_class inValue2Temp;
    inValue2Temp = inValue2;
    temp.mValue = inValue1.mValue / inValue2Temp;
    return temp;
  }

  Integer const operator%(Integer const& inValue1, int const& inValue2) {
    Integer temp;
    mpz_class inValue2Temp;
    inValue2Temp = inValue2;
    temp.mValue = inValue1.mValue % inValue2Temp;
    return temp;
  }

  Integer const operator+(int const& inValue1, Integer const& inValue2) {
    Integer temp;
    mpz_class inValue1Temp;
    inValue1Temp = inValue1;
    temp.mValue = inValue1Temp + inValue2.mValue;
    return temp;
  }

  Integer const operator-(int const& inValue1, Integer const& inValue2) {
    Integer temp;
    mpz_class inValue1Temp;
    inValue1Temp = inValue1;
    temp.mValue = inValue1Temp - inValue2.mValue;
    return temp;
  }

  Integer const operator*(int const& inValue1, Integer const& inValue2) {
    Integer temp;
    mpz_class inValue1Temp;
    inValue1Temp = inValue1;
    temp.mValue = inValue1Temp * inValue2.mValue;
    return temp;
  }

  Integer const operator/(int const& inValue1, Integer const& inValue2) {
    Integer temp;
    mpz_class inValue1Temp;
    inValue1Temp = inValue1;
    temp.mValue = inValue1Temp / inValue2.mValue;
    return temp;
  }

  Integer const operator%(int const& inValue1, Integer const& inValue2) {
    Integer temp;
    mpz_class inValue1Temp;
    inValue1Temp = inValue1;
    temp.mValue = inValue1Temp % inValue2.mValue;
    return temp;
  }

  Integer const operator+(Integer const& inValue1, Integer const& inValue2) {
    Integer temp;
    temp.mValue = inValue1.mValue + inValue2.mValue;
    return temp;
  }

  Integer const operator-(Integer const& inValue1, Integer const& inValue2) {
    Integer temp;
    temp.mValue = inValue1.mValue - inValue2.mValue;
    return temp;
  }

  Integer const operator*(Integer const& inValue1, Integer const& inValue2) {
    Integer temp;
    temp.mValue = inValue1.mValue * inValue2.mValue;
    return temp;
  }

  Integer const operator/(Integer const& inValue1, Integer const& inValue2) {
    Integer temp;
    temp.mValue = inValue1.mValue / inValue2.mValue;
    return temp;
  }

  Integer const operator%(Integer const& inValue1, Integer const& inValue2) {
    Integer temp;
    temp.mValue = inValue1.mValue % inValue2.mValue;
    return temp;
  }

  std::ostream& operator<<(std::ostream& ostr, Integer const& inValue) {
    ostr << inValue.mValue;
    return ostr;
  }

  std::istream& operator>>(std::istream& istr, Integer& inValue) {
    Integer temp;
    istr >> temp.mValue;
    inValue.mValue = temp.mValue;
    return istr;
  }

  bool operator<(Integer const& inValue1, int const& inValue2) {
    int tempReturn = 0;
    mpz_class operand1 = inValue1.mValue;
    mpz_class operand2 = inValue2;
    tempReturn = mpz_cmp(operand1.get_mpz_t(), operand2.get_mpz_t());
    if (tempReturn < 0) return true;
    else return false;
  }

  bool operator<=(Integer const& inValue1, int const& inValue2) {
    int tempReturn = 0;
    mpz_class operand1 = inValue1.mValue;
    mpz_class operand2 = inValue2;
    tempReturn = mpz_cmp(operand1.get_mpz_t(), operand2.get_mpz_t());
    if (tempReturn <= 0) return true;
    else return false;
  }

  bool operator>(Integer const& inValue1, int const& inValue2) {
    int tempReturn = 0;
    mpz_class operand1 = inValue1.mValue;
    mpz_class operand2 = inValue2;
    tempReturn = mpz_cmp(operand1.get_mpz_t(), operand2.get_mpz_t());
    if (tempReturn > 0) return true;
    else return false;
  }

  bool operator>=(Integer const& inValue1, int const& inValue2) {
    int tempReturn = 0;
    mpz_class operand1 = inValue1.mValue;
    mpz_class operand2 = inValue2;
    tempReturn = mpz_cmp(operand1.get_mpz_t(), operand2.get_mpz_t());
    if (tempReturn >= 0) return true;
    else return false;
  }

  bool operator==(Integer const& inValue1, int const& inValue2) {
    int tempReturn = 0;
    mpz_class operand1 = inValue1.mValue;
    mpz_class operand2 = inValue2;
    tempReturn = mpz_cmp(operand1.get_mpz_t(), operand2.get_mpz_t());
    if (tempReturn == 0) return true;
    else return false;
  }

  bool operator<(int const& inValue1, Integer const& inValue2) {
    int tempReturn = 0;
    mpz_class operand1 = inValue1;
    mpz_class operand2 = inValue2.mValue;
    tempReturn = mpz_cmp(operand1.get_mpz_t(), operand2.get_mpz_t());
    if (tempReturn < 0) return true;
    else return false;
  }

  bool operator<=(int const& inValue1, Integer const& inValue2) {
    int tempReturn = 0;
    mpz_class operand1 = inValue1;
    mpz_class operand2 = inValue2.mValue;
    tempReturn = mpz_cmp(operand1.get_mpz_t(), operand2.get_mpz_t());
    if (tempReturn <= 0) return true;
    else return false;
  }

  bool operator>(int const& inValue1, Integer const& inValue2) {
    int tempReturn = 0;
    mpz_class operand1 = inValue1;
    mpz_class operand2 = inValue2.mValue;
    tempReturn = mpz_cmp(operand1.get_mpz_t(), operand2.get_mpz_t());
    if (tempReturn > 0) return true;
    else return false;
  }

  bool operator>=(int const& inValue1, Integer const& inValue2) {
    int tempReturn = 0;
    mpz_class operand1 = inValue1;
    mpz_class operand2 = inValue2.mValue;
    tempReturn = mpz_cmp(operand1.get_mpz_t(), operand2.get_mpz_t());
    if (tempReturn >= 0) return true;
    else return false;
  }

  bool operator==(int const& inValue1, Integer const& inValue2) {
    int tempReturn = 0;
    mpz_class operand1 = inValue1;
    mpz_class operand2 = inValue2.mValue;
    tempReturn = mpz_cmp(operand1.get_mpz_t(), operand2.get_mpz_t());
    if (tempReturn == 0) return true;
    else return false;
  }

  bool operator<(Integer const& inValue1, Integer const& inValue2) {
    int tempReturn = 0;
    mpz_class operand1 = inValue1.mValue;
    mpz_class operand2 = inValue2.mValue;
    tempReturn = mpz_cmp(operand1.get_mpz_t(), operand2.get_mpz_t());
    if (tempReturn < 0) return true;
    else return false;
  }

  bool operator<=(Integer const& inValue1, Integer const& inValue2) {
    int tempReturn = 0;
    mpz_class operand1 = inValue1.mValue;
    mpz_class operand2 = inValue2.mValue;
    tempReturn = mpz_cmp(operand1.get_mpz_t(), operand2.get_mpz_t());
    if (tempReturn <= 0) return true;
    else return false;
  }

  bool operator>(Integer const& inValue1, Integer const& inValue2) {
    int tempReturn = 0;
    mpz_class operand1 = inValue1.mValue;
    mpz_class operand2 = inValue2.mValue;
    tempReturn = mpz_cmp(operand1.get_mpz_t(),operand2.get_mpz_t());
    if (tempReturn > 0) return true;
    else return false;
  }

  bool operator>=(Integer const& inValue1, Integer const& inValue2) {
    int tempReturn = 0;
    mpz_class operand1 = inValue1.mValue;
    mpz_class operand2 = inValue2.mValue;
    tempReturn = mpz_cmp(operand1.get_mpz_t(),operand2.get_mpz_t());
    if (tempReturn >= 0) return true;
    else return false;
  }

  bool operator==(Integer const& inValue1, Integer const& inValue2) {
    int tempReturn = 0;
    mpz_class operand1 = inValue1.mValue;
    mpz_class operand2 = inValue2.mValue;
    tempReturn = mpz_cmp(operand1.get_mpz_t(),operand2.get_mpz_t());
    if (tempReturn == 0) return true;
    else return false;
  }

  Integer const abs(Integer const& inValue) {
    Integer temp;
    temp.mValue = abs(inValue.mValue);
    return temp;
  }

/*! \brief Computes the Greatest Common Denominator (GCD).
 *
 *  @param inValue1 value for the first operand of datatype Integer
 *  @param inValue2 value for the second operand of datatype Integer
 *
 *  @return an Integer datatype value
 */
  Integer const gcd(Integer const& inValue1, Integer const& inValue2) {
    Integer temp;
    mpz_class tempReturn;
    mpz_class operand1 = inValue1.mValue;
    mpz_class operand2 = inValue2.mValue;
    mpz_gcd(tempReturn.get_mpz_t(),operand1.get_mpz_t(),operand2.get_mpz_t());
    temp.mValue = tempReturn;
    return temp;
  }


/*! \brief Computes the Least Common Multiple (LCM).
 *
 *  @param inValue1 value for the first operand of datatype Integer
 *  @param inValue2 value for the second operand of datatype Integer
 *
 *  @return an Integer datatype value
 */
  Integer const lcm(Integer const& inValue1, Integer const& inValue2) {
    Integer temp;
    mpz_class tempReturn;
    mpz_class operand1 = inValue1.mValue;
    mpz_class operand2 = inValue2.mValue;
    mpz_lcm(tempReturn.get_mpz_t(),operand1.get_mpz_t(),operand2.get_mpz_t());
    temp.mValue = tempReturn;
    return temp;
  }

  Integer const pow(Integer& inValue1, uli_t const& inValue2) {
    Integer temp;
    mpz_class tempReturn;
    mpz_class operand1 = inValue1.getValue();
    mpz_pow_ui(tempReturn.get_mpz_t(), operand1.get_mpz_t(), inValue2);
    temp.setValue(tempReturn);
    return temp;
  }

}

#endif
